package consumer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import proxy.ServerNode;
import proxy.UnitContainer;
import proxy.VideoUnit;
import simulation.Simulator;

/***
 * 
 * @author Anita
 * works with non-hormone proxy
 *
 */
public class UnitRequestConsumer extends Consumer {

	public UnitRequestConsumer(int id, Simulator simulator, ServerNode server,
			Random rand) {
		super(id, simulator, server, rand);
		noFavorites = noViewings;
	}

	/**
	 * chooseFavorites traverses through the list of all videos and fills the
	 * favoriteList with the videoUnit objects which are most interesting for
	 * this user. It also calculates sumDistances which is used to determine the
	 * probabilities of a unit being chosen.
	 */

	public void chooseFavorites() {
		// empty favorites list
		sumDistances = 0.0;

		LinkedList<VideoUnit> topFavorites = new LinkedList<VideoUnit>();
		favoriteList = new LinkedList<VideoUnit>();
		for (int i = 0; i < simulator.getVideoUnitDirectory().size(); i++) {
			// CLONE IT!!!
			VideoUnit vu = (VideoUnit) simulator.getVideoUnitDirectory().get(i)
					.clone();
			vu.calculateDistance(likesContent);

			// if we have enough we can pick one that is similar enough to our
			// already chosen things
			if (i > noFavorites) {
				if (vu.getDistance() > topFavorites.get(0).getDistance()) {
					continue;
				}
			}
			topFavorites.add(vu);
		}
		interestRadius = Collections.max(topFavorites).getDistance();

		for (int i = 0; i < topFavorites.size(); i++) {
			// probability to view a video is based on following formula

			favoriteList.add(topFavorites.get(i));
			sumDistances += (interestRadius - topFavorites.get(i).getDistance());
		}

	}

	/**
	 * Check if specific unit is present - then present immediately
	 */
	public void process() {
		boolean newRequest = false;
		// 1) fill up viewings
		if (viewings.size() == 0) {
			generateNewRequest();
			newRequest = true;
			server.countRequest(viewings);
			requestSize = viewings.size();
			missed = 0;
		}
		// 1a) request to be fulfilled
		ArrayList<Viewing> stillToDeliver = new ArrayList<Viewing>();
		Iterator<Viewing> it = null;
		for (it = viewings.iterator(); it.hasNext();) {
			Viewing vi = it.next();
			// 2) present local units to client and request others
			if (this.simulator.getSimTime() < vi.getViewStartTime()) {
				// specific unit has to be present
				UnitContainer result = this.server.searchPresentUnit(vi
						.getVideoUnit().getId());

				if (result != null) {
					presentSequentialLocalUnit(vi, result);
				} else {
					// deadline missed - we don't request it anymore!
					if (!checkDeadline(vi))
						stillToDeliver.add(vi);
				}
			}
			// 3) cleanup presentations that are done
			else if (simulator.getSimTime() > vi.getViewEndTime()) {
				// possibly the user now adjusts her taste to what she has
				// just
				// seen
				if (getRand().nextDouble() < getpMindChange()) {
					for (int j = 0; j < likesContent.length; j++) {
						likesContent[j] = vi.getVideoUnit().getContent().content[j];
					}
					// favorite videos must be recalculated for user's new
					// taste
					chooseFavorites();
				}

				it.remove();
				// request is fulfilled if all units are delivered
				cleanupViewings();
			}
		}
		// all units that have still to be delivered
		server.increaseInterest(stillToDeliver, newRequest, false);
	}

	/*
	 * Mixed requests consist of sequences connected by parallels so the request
	 * can be showed if all units are there 1) new request 2) still waiting for
	 * units 3) presentation done (u1<u2)||(u3<u4)
	 */
	public void processMixed() {
		boolean newRequest = false;
		boolean delivery = false;
		ArrayList<UnitContainer> ucs = new ArrayList<UnitContainer>();

		ArrayList<Viewing> seq1 = new ArrayList<Viewing>();
		ArrayList<Viewing> seq2 = new ArrayList<Viewing>();
		int reqsize;
		if (viewings.size() == 0) {
			// new request
			generateNewRequest();
			reqsize = viewings.size();

			newRequest = true;
			if (reqsize == 1) {
				seq1.add(viewings.get(0));
			} else {
				for (int i = 0; i < viewings.size() / 2; i++) {
					seq1.add(viewings.get(i));
					seq2.add(viewings.get(viewings.size() - i - 1));
				}
				if (reqsize % 2 != 0) {
					seq2.add(viewings.get(viewings.size() / 2 + 1));
				}
			}
		} else {
			// check if presenting mode or delivery mode
			for (Viewing vi : viewings) {
				if (vi.getViewStartTime() == Double.MAX_VALUE) {
					delivery = true;
					break;
				}
			}
		}

		if (delivery || newRequest) {

			int stillToDeliver = 0;
			for (Viewing vi : viewings) {
				UnitContainer uc = server.searchPresentUnit(vi.getVideoUnit()
						.getId());
				if (uc != null && !ucs.contains(uc)) {
					// lock unit (set is in use)
					uc.viewedUntil = vi.getViewEndTime();

					ucs.add(uc);
					if (seq1.contains(vi)) {
						seq1.remove(vi);
					} else {
						seq2.remove(vi);
					}

				} else if (uc == null) {
					stillToDeliver++;
				}
			}
			if (stillToDeliver == 0) {
				// all there - start to present all
				presentMixedUnits(ucs, seq1);
			} else {

				server.increaseInterest(seq1, newRequest, true);
				if (seq2.size() > 0) {
					server.increaseInterest(seq2, newRequest, true);
				}
			}
		} else {
			// presenting or presentation done
			for (int i = 0; i < viewings.size(); i++) {
				if (simulator.getSimTime() >= viewings.get(i).getViewEndTime()) {
					if (getRand().nextDouble() < getpMindChange()) {
						likesContent = Arrays.copyOf(viewings.get(i)
								.getVideoUnit().getContent().content,
								likesContent.length);
						this.chooseFavorites();
					}
					viewings.remove(i);
				} else {
					addUnitUtilizationStat();
					// presenting ... nothing else to do
					return;
				}
			}
			if (viewings.size() == 0) {
				simulator.requestFulfilledCounter++;
			}
		}
	}

	/*
	 * Parallel requests can be presented if all units are available otherwise,
	 * wait! 1) new request 2) still waiting for units 3) presenting 4)
	 * presentation done
	 */
	public void processParallel() {

		boolean newRequest = false;
		boolean delivery = false;

		if (viewings.size() == 0) {
			// new request
			generateNewRequest();
			newRequest = true;
		} else {
			// check if presenting mode or delivery mode
			for (Viewing vi : viewings) {
				if (vi.getViewStartTime() == Double.MAX_VALUE) {
					delivery = true;
					break;
				}
			}
		}

		if (delivery || newRequest) {
			// delivery mode
			ArrayList<UnitContainer> ucs = new ArrayList<UnitContainer>();
			ArrayList<Viewing> stillToDeliver = new ArrayList<Viewing>();

			for (Viewing vi : viewings) {

				UnitContainer uc = server.searchPresentUnit(vi.getVideoUnit()
						.getId());
				if (uc != null && !ucs.contains(vi)) {
					// lock unit (set is in use)
					uc.viewedUntil = vi.getViewEndTime();
					ucs.add(uc);
				} else if (uc == null) {
					stillToDeliver.add(vi);
				}
			}

			if (stillToDeliver.size() == 0) {
				// all there - start to present all
				presentParallelUnits(ucs);
			} else {
				server.increaseInterest(stillToDeliver, newRequest, false);
			}
		} else {
			// presenting or presentation done
			for (int i = 0; i < viewings.size(); i++) {
				if (simulator.getSimTime() >= viewings.get(i).getViewEndTime()) {
					// maybe adjust taste
					if (getRand().nextDouble() < getpMindChange()) {
						likesContent = Arrays.copyOf(viewings.get(i)
								.getVideoUnit().getContent().content,
								likesContent.length);
						this.chooseFavorites();
					}
					viewings.remove(i);
				} else {
					addUnitUtilizationStat();
					// presenting ... nothing else to do
					return;
				}
			}
			if (viewings.size() == 0) {
				simulator.requestFulfilledCounter++;
			}
		}

	}

	/**
	 * A request consists of a sequence of units. All units are requested at the
	 * same time, but with different priority. One unit at a time is presented
	 * to the user
	 */
	public void processSequence() {

		// check if there is something to present
		if (currentlyViewing == null) {

			if (viewings.size() == 0) {
				generateNewRequest();
				server.increaseInterest(viewings, true, true);
			} else {
				// there is a request that has to be fulfilled
				// check if the unit is local and can be started
				Viewing next = viewings.get(0);

				UnitContainer uc = server.searchPresentUnit(next.getVideoUnit()
						.getId());
				List<Viewing> currentlyDemanding = viewings;
				if (uc != null) {
					// is local, but not started yet
					presentSequentialLocalUnit(next, uc);
					currentlyDemanding = viewings.subList(1, viewings.size());
				}

				server.increaseInterest(currentlyDemanding, false, true);
			}
		} else {
			// check if unit is done, delete from list
			if (simulator.getSimTime() >= currentlyViewing.getViewEndTime()) {
				if (getRand().nextDouble() < getpMindChange()) {
					likesContent = Arrays.copyOf(currentlyViewing
							.getVideoUnit().getContent().content,
							likesContent.length);
					this.chooseFavorites();
				}
				handleSeqUnitPresentationDone();
			} else {
				simulator.unitsCurrentlyInPresentation.add(1 / simulator
						.unitCount(currentlyViewing.getVideoUnit().getId()));// currently
				server.increaseInterest(viewings.subList(1, viewings.size()),
						false, true);
			}
		}
	}

}
